package com.ask.forMe.service.impl;

import com.ask.forMe.model.entity.Item;
import com.ask.forMe.mapper.RatingMapper;
import com.ask.forMe.service.ItemService;
import com.ask.forMe.service.RatingService;
import com.ask.forMe.service.RecommendService;
import com.ask.forMe.utils.SimilarityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.ask.forMe.constant.RedisConstant.RECOMMEND_DAILY_KEY;
import static com.ask.forMe.constant.RedisConstant.SEVEN_DAYS;


@Service
public class RecommendServiceImpl implements RecommendService {
    @Autowired
    private RatingMapper ratingMapper;
    @Autowired
    private RatingService ratingService;
    @Autowired
    private ItemService itemService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 为用户推荐相似商品
     *
     * @param userId
     * @param topN
     * @return
     */
    @Override
    public List<Long> getItemRecommendationsForUser(Long userId, int topN) {
        // 1. 获取用户评分过的商品id
        List<Long> userRatedItemIds = ratingMapper.getItemIdByUserId(userId);

        // todo 冷启动 用户为新用户，或未进行过任何评分
        if (userRatedItemIds.isEmpty()) {
            return itemService.getHotBySales(topN); // 返回热门商品
        }

        // 2. 获取商品-用户评分矩阵 todo 可缓存
        Map<Long, Map<Long, Double>> itemUserRatings = ratingService.getItemUserRatings();


        // Key：商品id(用户未评过) Value：该商品与用户所有已评分商品的总相似度
        Map<Long, Double> candidateItemSimilarities = new HashMap<>();

        // 3. 遍历用户评分过的商品，计算商品间的相似度
        for (Long ratedItemId : userRatedItemIds) {
            // 获取当前物品的用户评分向量
            Map<Long, Double> ratedVector = itemUserRatings.get(ratedItemId);
            // 遍历所有物品
            for (Long candidateItemId : itemUserRatings.keySet()) {
                if (!userRatedItemIds.contains(candidateItemId)) { // 过滤掉评分过的商品（只会推荐未评分过的商品）
                    Map<Long, Double> candidateItemVector = itemUserRatings.get(candidateItemId);
                    // 计算两个物品之间的相似度
                    double similarity = SimilarityUtils.calculateCosineSimilarity(ratedVector, candidateItemVector);
                    // 累加相似度到一个键值对上
                    candidateItemSimilarities.merge(candidateItemId, similarity, Double::sum);
                }
            }
        }

        Set<Long> past7DaysRecommendedIds = getPast7DaysRecommendedIds(userId);

        // 4. 获取推荐的商品id, 排除近7天已经推荐过的，截取相似度最高的topN个
        List<Long> topNRecommendations =
                candidateItemSimilarities.entrySet().stream()
                        .filter(entry -> !past7DaysRecommendedIds.contains(entry.getKey()))
                        .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue()))
                        .limit(topN)
                        .map(Map.Entry::getKey)
                        .toList();
        return topNRecommendations;
    }

    /**
     * 从redis中获取近7天推荐过的商品ID
     *
     * @param userId
     * @return
     */
    @Override
    public Set<Long> getPast7DaysRecommendedIds(Long userId) {
        List<String> keys = new ArrayList<>(SEVEN_DAYS);
        LocalDate today = LocalDate.now();
        for (int i = 0; i <= SEVEN_DAYS; i++) {
            String redisKey = today.minusDays(i).format(DateTimeFormatter.BASIC_ISO_DATE)
                    + ":" + RECOMMEND_DAILY_KEY + userId;
            keys.add(redisKey);
        }
        if (keys.isEmpty()) return Collections.emptySet();
        Set<Long> cachedRecommendationIds = redisTemplate.opsForSet().union(keys);
        return cachedRecommendationIds != null ? cachedRecommendationIds : Collections.emptySet();
    }

    /**
     * 用户点击<换一批>，生成新的一批推荐
     *
     * @param userId
     */
    @Override
    public List<Item> refreshRecommendation(Long userId, int topN) {
        List<Long> itemIds = getItemRecommendationsForUser(userId, topN);
        if (itemIds == null || itemIds.isEmpty())
                return Collections.emptyList();
        List<Item> items = itemService.listByIds(itemIds);
        saveRecommendationIds2Redis(userId, itemIds);
        return items;
    }

    /**
     * 存储生成的推荐列表至redis
     * @param userId
     * @param itemIds
     */
    public void saveRecommendationIds2Redis(Long userId, List<Long> itemIds) {
        String key = LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE)
                + ":" + RECOMMEND_DAILY_KEY + userId;
        itemIds.forEach(itemId -> {
            redisTemplate.opsForSet().add(key, itemId);
        });
        // 设置过期时间为7天
        redisTemplate.expire(key, SEVEN_DAYS, TimeUnit.DAYS);
    }
}
